//=========================================================================
// Copyright (C) 2024 The C++ Component Model(COMO) Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//=========================================================================

// #include <sys/socket.h>  // socket()、sockaddr、bind()、recvfrom()、sendto()
#include <stdio.h>          // perror()、printf()
#include <stdlib.h>         // exit()
// #include <netinet/in.h>  // sockaddr_in、（htons()）
#include <string.h>         // bzero()、strncpy()
#include <arpa/inet.h>      // inet_pton()
#include <unistd.h>         // close()
#include <stdint.h>
#include "rudp.h"
#include "comm.h"           //g_buff_size

Rudp rudp;
int sock_fd;
struct sockaddr_in serv_addr; // server socket struct
struct sockaddr_in clie_addr; // client socket struct
socklen_t serv_addr_len;
socklen_t clie_addr_len;
char msg_send[g_buff_size];   // msg buffer
char msg_recv[g_buff_size];   // msg buffer


void dump(RudpPackage *p)
{
    int idx = 0;
    printf("%d : ", idx++);
    while (p) {
        printf("(");
        int i;
        for (i = 0; i < p->sz; i++) {
            printf("%02x ", (uint8_t)p->buffer[i]);
        }
        printf(") ");
        p = p->next;
    }
    printf("\n");
}

void dump_recv(Rudp *U)
{
    char tmp[MAX_PACKAGE];
    int n;
    while ((n = RudpRecv(U, tmp))) {
        if (n < 0) {
            printf("CORRPUT\n");
            break;
        }

        // Original way to print, replaced by print_hex()
        // printf("RECV ");
        // for (int i = 0; i < n; i++) {
        //     printf("%02x ", (uint8_t)tmp[i]);
        // }
        // printf("\n");
        print_hex(tmp, n);
    }
}

// Print bytes in the buffer (in hexadecimal form)
void print_hex(char *readbuf, int nread)
{
    for (int i = 0; i < nread; i++) {
        printf("%02X ", (unsigned char)readbuf[i]);
        if (i % 8 == 7) {
            printf("\n");
        }
    }
    // Print \n in the last line if < 8 bytes
    if (nread % 8 != 0) {
        printf("\n");
    }
}

size_t udp_send(RudpPackage *p, bool called_by_client)
{
    // Send package
    memcpy(msg_send, p->buffer, p->sz);

    size_t size_send;
    if (called_by_client) {
        size_send = sendto(sock_fd, msg_send, p->sz, 0, 
                            (struct sockaddr *)(&serv_addr), serv_addr_len);
    }
    else {
        size_send = sendto(sock_fd, msg_send, p->sz, 0, 
                            (struct sockaddr *)(&clie_addr), clie_addr_len);
    }

    if (-1 == size_send)
    {
        if (-1 == close(sock_fd))
        {
            perror("sendto() close() error");
            exit(EXIT_FAILURE);
        }
        perror("sendto() error");
        exit(EXIT_FAILURE);
    }
    printf("Sent package size = %zu\n", size_send);
    // Here we use print_hex() as a substitute of dump()
    // dump(p);
    print_hex(msg_send, size_send);

    return size_send;
}

size_t udp_recv(bool called_by_client)
{
    size_t size_recv;
    // Receiver doesn't know the exact size of message, so recv max buffsize
    if (called_by_client) {
        size_recv = recvfrom(sock_fd, msg_recv, g_buff_size, 0, 
                        (struct sockaddr *)(&serv_addr), &serv_addr_len);
    }
    else {
        size_recv = recvfrom(sock_fd, msg_recv, g_buff_size, 0, 
                        (struct sockaddr *)(&clie_addr), &clie_addr_len);
    }

    // recvfrom() error
    if (-1 == size_recv)
    {
        if (-1 == close(sock_fd))
        {
            perror("recvfrom() close() error");
            exit(EXIT_FAILURE);
        }
        perror("recvfrom() error");
        exit(EXIT_FAILURE);
    }
    printf("Received package size = %zu\n", size_recv);
    
    return size_recv;
}

void socket_init(bool called_by_client, int port)
{
    // Create socket & get fd
    if ((sock_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1)
    {
        perror("socket() error");
        exit(EXIT_FAILURE);
    }
    printf("Socket fd=%d created\n",sock_fd);

    // Init serv_addr & serv_addr_len
    bzero(&serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(g_serv_port);
    serv_addr_len = sizeof(serv_addr);

    int ret;
    if (called_by_client) {
        ret = inet_pton(AF_INET, "0.0.0.0", &serv_addr.sin_addr);
    }
    else {
        ret = inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr);
    }
    if (ret != 1)
    {
        perror("inet_pton() error");
        exit(EXIT_FAILURE);
    }

    // Init clie_addr & clie_addr_len
    bzero(&clie_addr, sizeof(clie_addr));
    clie_addr_len = sizeof(clie_addr);
    
    // Init msg_recv & msg_send
    bzero(&msg_recv, sizeof(*msg_recv));
    bzero(&msg_send, sizeof(*msg_send));

    // For server, bind socket fd with serv_addr
    if (!called_by_client && (bind(sock_fd, (struct sockaddr *)(&serv_addr), serv_addr_len)) == -1)
    {
        if ((close(sock_fd)) == -1)
        {
            perror("bind() close() error");
            exit(EXIT_FAILURE);
        }

        perror("bind() error");
        exit(EXIT_FAILURE);
    }
}

void socket_close()
{
    if (-1 == (close(sock_fd)))
    {
        perror("close() error");
        exit(EXIT_FAILURE);
    }
}
